using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Text.RegularExpressions;
using System.Collections;
using System.IO;

namespace net.notdot.Attercop.Client
{
    /// <summary>
    /// Creates indexes for file listings, and provides facilities for searching them.
    /// </summary>
    public class FileListingIndex
    {
        static char[] separators = new char[] { ' ', '.', '_', '-', '[', ']', '(', ')', '<', '>', '\'', '"' };

        private Dictionary<string, List<PathNode>> index = new Dictionary<string, List<PathNode>>();
        private Dictionary<string, List<PathNode>> hashIndex = new Dictionary<string, List<PathNode>>();
        int indexSize = 0;

        /// <summary>
        /// Creates a new FileListingIndex
        /// </summary>
        /// <param name="root"></param>
        public FileListingIndex()
        {
            
        }

        public void AddNode(PathNode node) {
            AddNode(node, true);
        }

        /// <summary>
        /// Adds the specified node and all its descendents to the index
        /// </summary>
        /// <param name="node"></param>
        public void AddNode(PathNode node, bool AddDescendents)
        {
            //Add the node to the index under each term
            for (int i = 0; i < node.Terms.Length; i++)
            {
                if(node.Terms[i] != "") {
                    if (!index.ContainsKey(node.Terms[i]))
                        index.Add(node.Terms[i], new List<PathNode>());

                    index[node.Terms[i]].Add(node);
                }
            }

            //Add the node to the hash index, if it has a hash.
            //If it doesn't, register to be notified when it gets one.
            if (!node.IsDir)
            {
                if (node.Hash != null)
                {
                    string b64Hash = Convert.ToBase64String(node.Hash);
                    if (!hashIndex.ContainsKey(b64Hash))
                        hashIndex.Add(b64Hash, new List<PathNode>());
                    hashIndex[b64Hash].Add(node);
                }
                else
                {
                    node.HashCalculated += new EventHandler(node_HashCalculated);
                }
            }

            indexSize++;

            //Recurse for any child nodes
            if(AddDescendents) {
                foreach (PathNode subnode in node.Children)
                    AddNode(subnode);
            }
        }

        void node_HashCalculated(object sender, EventArgs e)
        {
            PathNode node = (PathNode)sender;
            string b64Hash = Convert.ToBase64String(node.Hash);
            if (!hashIndex.ContainsKey(b64Hash))
                hashIndex.Add(b64Hash, new List<PathNode>());
            hashIndex[b64Hash].Add(node);
        }

        public void RemoveNode(PathNode node) {
            List<PathNode> l;

            //Recurse for any child nodes
            foreach (PathNode subnode in node.Children)
                RemoveNode((PathNode)subnode);

            //Remove the node from the index under each term
            for (int i = 0; i < node.Terms.Length; i++)
            {
                l = index[node.Terms[i]];
                l.Remove(node);
                if (l.Count == 0)
                    index.Remove(node.Terms[i]);
            }

            //Remove the node from the hash index
            //TODO: Remove event handlers.
            if (node.Hash != null)
            {
                string b64Hash = Convert.ToBase64String(node.Hash);
                l = hashIndex[b64Hash];
                l.Remove(node);
                if (l.Count == 0)
                    hashIndex.Remove(b64Hash);
            }

            indexSize--;
        }

        public void TrimExcess()
        {
            foreach (KeyValuePair<string,List<PathNode>> l in index)
            {
                l.Value.TrimExcess();
            }
        }

        public List<PathNode> Search(string query, Predicate<PathNode> filter)
        {
            List<PathNode> indexEntries = new List<PathNode>();
            List<PathNode> ret = new List<PathNode>();

            string[] terms = GetTerms(query);

            for (int i = 0; i < terms.Length; i++)
            {
                if (!index.ContainsKey(terms[i]))
                    continue;

                indexEntries.AddRange(index[terms[i]]);
            }

            indexEntries.Sort();
            int newCount = RemoveDuplicates<PathNode>(indexEntries);
            indexEntries.RemoveRange(newCount, indexEntries.Count - newCount);
            
                foreach (PathNode current in indexEntries)
                {
                    if ((current.IsDir || current.Hash != null) && NodeMatches(current, terms))
                    {
                        if(filter == null || filter(current)) {
                            ret.Add(current);
                        }
                    }
                }

            return ret;
        }

        private bool NodeMatches(PathNode node, string[] terms)
        {
            bool[] matches = new bool[terms.Length];
            int matchesLeft = terms.Length;

            for (PathNode current = node; current != null; current = (PathNode) current.Parent)
            {
                int i = 0, j = 0;
                while (i < terms.Length && j < current.Terms.Length)
                {
                    int result = terms[i].CompareTo(current.Terms[j]);
                    if (result < 0)
                    {
                        i++;
                    }
                    else if (result == 0)
                    {
                        if (!matches[i])
                        {
                            matches[i] = true;
                            matchesLeft--;
                        }

                        if (matchesLeft == 0)
                            return true;

                        i++;
                        j++;
                    }
                    else
                    {
                        j++;
                    }
                }
            }

            return false;
        }

        public static string[] GetTerms(string str)
        {
            string str2 = Path.ChangeExtension(str, "").ToLower(); ;

            string[] terms = str2.Split(separators, StringSplitOptions.RemoveEmptyEntries);
            if (terms.Length == 0)
                terms = str.ToLower().Split(separators, StringSplitOptions.RemoveEmptyEntries);
            
            Array.Sort<string>(terms);
            Array.Resize<string>(ref terms, RemoveDuplicates<string>(terms));

            return terms;
        }

        private static int RemoveDuplicates<T>(IList<T> a) 
        {
            if (a.Count == 0)
                return 0;

            int j = 0;
            for (int i = 1; i < a.Count; i++)
            {
                if(!a[j].Equals(a[i])) {
                    j++;
                    a[j] = a[i];
                }
            }

            return j + 1;
        }
    }
}
